﻿using Actor.Net;
using Actor.Net.Binder;
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using System.Reflection;
using Actor.Net.Network.Actor.Adapter;
using Actor.Net.Network.Actor;
using Actor.Net.Message;
using Actor.Net.Exceptions;
using System.IO;

namespace Actor.Net
{
    /// <summary>
    /// Actor消息适配器
    /// </summary>
    public class ActorMessageAdapter : IActorAdapter
    {
        private static int startOffset = ActorPacketParser.HeadSize;

        /// <summary>
        /// 网络IO接口实现
        /// </summary>
        /// <param name="context"></param>
        /// <param name="input"></param>
        public async void DispatchAdapter(ActorTransferContext context)
        {
            var actorInfo = new ActorMessageResponse();

            var receiveStream = context.BodyStream;
            var routerInfo = MessagePack.MessagePackSerializer.Deserialize<ActorMessageRequest>(receiveStream);
            var parameterInfos = ActorBinder.GetRouterMethodParameters(routerInfo.ServerGroup, routerInfo.ActorType, routerInfo.Router);
            var method = ActorBinder.GetRouterMethod(routerInfo.ServerGroup, routerInfo.ActorType, routerInfo.Router);

            object methodParameter = null;
            if (receiveStream.Position < receiveStream.Length)
                methodParameter = MessagePack.MessagePackSerializer.Deserialize(parameterInfos[0].ParameterType, receiveStream);

            var isNotResponse = false;
            try
            {
                if (method == null)
                {
                    actorInfo.Stated = ActorCallState.RouterNotBind;
                    var ex = new ActorServerException($"ServerGroup:{routerInfo.ServerGroup} ActorType:{routerInfo.ActorType} Router:{routerInfo.Router} does not have route binding");
                    context.ChannelContext.Channel.NetService.ProcessSocketError(context.ChannelContext, ex);
                }

                if (method.ReturnType == typeof(void))
                    isNotResponse = true;

                if (method.ReturnType == typeof(Task))
                    isNotResponse = true;

                var actor = ActorBinder.GetLocalActor(routerInfo.ServerGroup, routerInfo.ActorType, routerInfo.ActorId);
                if (actor == null)
                {
                    actorInfo.Stated = ActorCallState.ActorNotExisted;
                    if (!isNotResponse)
                    {
                        context.Output(actorInfo);
                    }
                    else
                    {
                        var ex = new ActorServerException($"ServerGroup:{routerInfo.ServerGroup} ActorType:{routerInfo.ActorType} actor not exist.");
                        context.ChannelContext.Channel.NetService.ProcessSocketError(context.ChannelContext, ex);
                    }
                    return;
                }
                
                var excuteAttribute = ActorBinder.GetActorExcuteAttribute(routerInfo.ServerGroup, routerInfo.ActorType, routerInfo.Router);
                if (excuteAttribute == null)
                {
                    var ex = new ArgumentNullException($"Method {method.Name} lacks ActorExcuteAttribute attribute.");
                    context.ChannelContext.Channel.NetService.ProcessSocketError(context.ChannelContext, ex);
                    return;
                }

                IParameter parameter = excuteAttribute.GetParameter(actor, method);
                if (method.ReturnType == typeof(void))
                {
                    ((IReturnVoidParameter)parameter).StartFunc(actor, method, methodParameter);
                    return;
                }
                else if(method.ReturnType == typeof(Task))
                {
                    await ((IReturnVoidParameter)parameter).StartFuncAsync(actor, method, methodParameter);
                    return;
                }
                else if(method.ReturnType.BaseType == typeof(Task))
                {
                    var result = await ((IReturnTaskResultParameter)parameter).StartFuncAsync(actor, method, methodParameter);
                    this.Response(context, actorInfo, result);
                    return;
                }
            }
            catch(Exception ex)
            {
                if (!isNotResponse)
                {
                    this.PostException(context, ex, actorInfo);
                }
            }
        }

        private void PostException(ActorTransferContext context, Exception exception, ActorMessageResponse actorInfo)
        {
            context.ChannelContext.Channel.NetService.ProcessSocketError(context.ChannelContext, exception);
            actorInfo.Stated = ActorCallState.ActorServerException;
            actorInfo.ExceptionInfo = exception.ToString();
            this.Response(context, actorInfo, null);
        }

        private void Response(ActorTransferContext context, ActorMessageResponse actorInfo, object data)
        {
            var sendStream = context.ChannelContext.SendStream;
            var locker = context.ChannelContext.SendLocker;

            lock (locker)
            {
                try
                {
                    sendStream.Seek(startOffset, SeekOrigin.Begin);
                    MessagePack.MessagePackSerializer.Serialize(sendStream, actorInfo);

                    if (data != null)
                        MessagePack.MessagePackSerializer.Serialize(sendStream, data);

                    context.ChannelContext.SendActorMessage(sendStream, context.Command, context.RpcId, ActorSenderType.ResponseSender);
                }
                finally
                {
                    sendStream.Seek(0, SeekOrigin.Begin);
                    sendStream.SetLength(0);
                }
            }
        }
    }
}
